<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>KSQL Quickstart</title>
    <script>
        var rawResponseBody = '';
        var xhr = new XMLHttpRequest();

        var renderFunction = renderTabular;
        var streamedResponse = false;

        function sendRequest(resource) {
            xhr.abort();

            var ksql = document.getElementById('ksql').value;
            var properties = getProperties();

            xhr.onreadystatechange = function() {
                if (xhr.response !== '' && ((xhr.readyState === 3 && streamedResponse) || xhr.readyState === 4)) {
                    rawResponseBody = xhr.response;
                    renderResponse();
                }
                if (xhr.readyState === 4 || xhr.readyState === 0) {
                    document.getElementById('request_loading').hidden = true;
                    document.getElementById('cancel_request').hidden = true;
                }
            };

            var data = JSON.stringify({'ksql': ksql, 'streamsProperties': properties});

            document.getElementById('cancel_request').hidden = false;
            document.getElementById('request_loading').hidden = false;
            xhr.open('POST', resource);
            xhr.setRequestHeader('Content-Type', 'application/json');
            xhr.send(data);
        }

        function cancelRequest() {
            var responseElement = document.getElementById('response');
            var response = responseElement.innerHTML;
            xhr.abort();
            responseElement.innerHTML = response;
        }

        function renderResponse() {
            var renderedBody = '';
            if (streamedResponse) {
                // Used to try to report JSON parsing errors to the user, but
                // since printed topics don't have a consistent format, just
                // have to assume that any parsing error is for that reason and
                // we can just stick with the raw message body for the output
                var splitBody = rawResponseBody.split('\n');
                for (var i = 0; i < splitBody.length; i++) {
                    var line = splitBody[i].trim();
                    if (line !== '') {
                        try {
                            var parsedJson = JSON.parse(line);
                            renderedBody += renderFunction(parsedJson);
                        } catch (SyntaxError) {
                            renderedBody += line;
                        }
                        renderedBody += '\n';
                    }
                }
            } else {
                try {
                    var parsedJson = JSON.parse(rawResponseBody);
                    renderedBody = renderFunction(parsedJson);
                } catch (SyntaxError) {
                    alert('Error parsing response JSON');
                    renderedBody = rawResponseBody;
                }
            }
            document.getElementById('response').innerHTML = renderedBody;
        }

        function renderTabular(parsedBody) {
            if (Array.isArray(parsedBody)) {
                // The response is a list of statement responses
                var result = [];
                for (var i = 0; i < parsedBody.length; i++) {
                    result.push(renderTabularStatement(parsedBody[i]));
                }
                return result.join('\n\n');
            } else if (parsedBody instanceof Object) {
                // The response is either an error or a streamed row
                var errorMessage = parsedBody.message || parsedBody.errorMessage;
                if (errorMessage) {
                    return parsedBody.message;
                } else if (parsedBody.row) {
                    var result = [];
                    var columns = parsedBody.row.columns;
                    for (var i = 0; i < columns.length; i++) {
                        // TODO: Figure out how to handle arrays/maps here...
                        result.push(columns[i].toString());
                    }
                    return ' ' + result.join(' | ') + ' ';
                } else {
                    throw SyntaxError;
                }
            } else {
                throw SyntaxError;
            }
        }

        function renderTabularStatement(statementResponse) {
            var columnHeaders, rowValues;
            if (statementResponse.currentStatus) {
                var innerBody = statementResponse.currentStatus;
                columnHeaders = ['Command ID', 'Status', 'Message'];
                rowValues = [[innerBody.commandId, innerBody.status, innerBody.message]];
            } else if (statementResponse.error) {
                var innerBody = statementResponse.error;
                return innerBody.message;
            } else if (statementResponse.streamsProperties) {
                var innerBody = statementResponse.streamsProperties;
                columnHeaders = ['Property', 'Value'];
                rowValues = [];
                var streamsProperties = innerBody.streamsProperties;
                for (var property in streamsProperties) {
                    if (!streamsProperties.hasOwnProperty(property)) {
                        continue;
                    }
                    rowValues.push([property, streamsProperties[property].toString()]);
                }
            } else if (statementResponse.queries) {
                var innerBody = statementResponse.queries;
                columnHeaders = ['ID', 'Kafka Topic', 'Query String'];
                rowValues = [];
                var queries = innerBody.queries;
                for (var i = 0; i < queries.length; i++) {
                    var query = queries[i];
                    rowValues.push([query.id.toString(), query.kafkaTopic, query.queryString]);
                }
            } else if (statementResponse.setProperty) {
                var innerBody = statementResponse.setProperty;
                columnHeaders = ['Property', 'Prior Value', 'New Value'];
                rowValues = [[innerBody.property, innerBody.oldValue, innerBody.newValue]];
            } else if (statementResponse.description) {
                var innerBody = statementResponse.description;
                columnHeaders = ['Field', 'Type'];
                rowValues = [];
                var fields = innerBody.schema.fields;
                for (var i = 0; i < fields.length; i++) {
                    var field = fields[i];
                    rowValues.push([field.field, field.type]);
                }
            } else if (statementResponse.streams) {
                var innerBody = statementResponse.streams;
                columnHeaders = ['Stream Name', 'Ksql Topic'];
                rowValues = [];
                var streams = innerBody.streams;
                for (var i = 0; i < streams.length; i++) {
                    var stream = streams[i];
                    rowValues.push([stream.name, stream.topic]);
                }
            } else if (statementResponse.tables) {
                var innerBody = statementResponse.tables;
                columnHeaders = ['Table Name', 'Ksql Topic', 'Statestore', 'Windowed'];
                rowValues = [];
                var tables = innerBody.tables;
                for (var i = 0; i < tables.length; i++) {
                    var table = tables[i];
                    rowValues.push([table.name, table.topic, table.stateStoreName, table.isWindowed.toString()]);
                }
            } else if (statementResponse.topics) {
                var innerBody = statementResponse.topics;
                columnHeaders = ['Topic Name', 'Kafka Topic', 'Format'];
                rowValues = [];
                var topics = innerBody.topics;
                for (var i = 0; i < topics.length; i++) {
                    var topic = topics[i];
                    rowValues.push([topic.name, topic.kafkaTopic, topic.format]);
                }
            } else {
                throw SyntaxError;
            }
            return renderTable(columnHeaders, rowValues);
        }

        function renderTable(columnHeaders, rowValues) {
            var lengths = [];

            for (var i = 0; i < columnHeaders.length; i++) {
                lengths.push(columnHeaders[i].length);
            }

            if (!rowValues || rowValues.length === 0) {
                return renderTableRow(columnHeaders, lengths);
            }

            for (var i = 0; i < rowValues.length; i++) {
                var row = rowValues[i];
                for (var j = 0; j < row.length; j++) {
                    lengths[j] = Math.max(lengths[j], row[j].length);
                }
            }

            var lengthsSum = lengths[0] + 2;
            for (var i = 1; i < lengths.length; i++) {
                lengthsSum += lengths[i] + 3;
            }

            var result = [
                renderTableRow(columnHeaders, lengths),
                Array(lengthsSum + 1).join('-')
            ];
            for (var i = 0; i < rowValues.length; i++) {
                result.push(renderTableRow(rowValues[i], lengths));
            }

            return result.join('\n');
        }

        function renderTableRow(values, lengths) {
            var result = [];
            for (var i = 0; i < values.length; i++) {
                result.push(pad(values[i], lengths[i] || 0));
            }
            return ' ' + result.join(' | ') + ' ';
        }

        function pad(str, len) {
            if (str.length >= len) {
                return str;
            }
            var pad = Array(len - str.length + 1).join(' ');
            return str + pad;
        }

        function renderPrettyJson(parsedBody) {
            return JSON.stringify(parsedBody, null, 2);
        }

        function renderCompactJson(parsedBody) {
            return JSON.stringify(parsedBody);
        }

        function updateFormat(newRenderFunction) {
            renderFunction = newRenderFunction;
            if (rawResponseBody !== '') {
                renderResponse();
            }
        }

        function addNewProperty() {
            var key = document.createElement('input');
            key.type = 'text';
            key.classList.add('property-key');

            var value = document.createElement('input');
            value.type = 'text';
            value.classList.add('property-value');

            var deleteButton = document.createElement('button');
            deleteButton.appendChild(document.createTextNode('X'));

            var propertySpan = document.createElement('span');
            propertySpan.classList.add('property');

            propertySpan.appendChild(key);
            propertySpan.appendChild(document.createTextNode(' '));
            propertySpan.appendChild(document.createTextNode('='));
            propertySpan.appendChild(document.createTextNode(' '));
            propertySpan.appendChild(value);
            propertySpan.appendChild(document.createTextNode(' '));
            propertySpan.appendChild(deleteButton);

            var propertyDiv = document.createElement('div');
            propertyDiv.classList.add('property');

            propertyDiv.appendChild(propertySpan);

            var propertiesElement = document.getElementById('properties');
            propertiesElement.appendChild(propertyDiv);

            deleteButton.onclick = function() {
                propertiesElement.removeChild(propertyDiv);
            }
        }

        function getProperties() {
            var properties = {};
            var key, value;
            var propertyElements = document.getElementById('properties').children;
            for (var i = 0; i < propertyElements.length; i++) {
                var propertyDiv = propertyElements[i];
                if (!propertyDiv.classList.contains('property')) {
                    continue;
                }
                var propertyDivChildren = propertyDiv.children;
                for (var j = 0; j < propertyDivChildren.length; j++) {
                    var propertySpan = propertyDivChildren[j];
                    if (!propertySpan.classList.contains('property')) {
                        continue;
                    }
                    var propertySpanChildren = propertySpan.children;
                    for (var k = 0; k < propertySpanChildren.length; k++) {
                        var propertyInput = propertySpanChildren[k];
                        if (propertyInput.classList.contains('property-key')) {
                            key = propertyInput.value;
                        } else if (propertyInput.classList.contains('property-value')) {
                            value = propertyInput.value;
                        }
                    }
                }
                if (key === '') {
                    continue;
                }
                properties[key] = value;
            }
            return properties;
        }

        window.onload = addNewProperty;
    </script>
    <style>
        #ksql-wrapper {
            width: 45%;
        }

        .request {
            width: 100%;
            height: 200px;

            font-size: 20px;
            font-family: monospace;
        }

        textarea.request {
            resize: none;
        }

        .properties-wrapper {
            height: 200px;
            width: 45%;
            position: absolute;
            right: 10px;
            top: 10px;
        }

        .newProperty {
            margin: 10px 0;
        }

        #properties {
            height: 100%;
            width: 100%;

            margin-top: 5px;

            overflow-y: scroll;
        }

        .property {
            padding: 5px 0;
            margin: 5px 0;
        }

        span.property {
            padding: 5px;
            margin: 5px 0;
        }

        .loading {
            animation: pulse 2s infinite;
        }

        @keyframes pulse {
            0% {
                opacity: 0.1;
            }
            50% {
                opacity: 1;
            }
            100% {
                opacity: 0.1;
            }
        }

        .response {
            width: 100%;
            height: 1000px;

            overflow-y: scroll;

            border: 2px solid gray;

            font-size: 20px;
        }
    </style>
</head>
<body>
    <div id="ksql-wrapper">
        KSQL:
        <br />
        <textarea class="request" id="ksql"></textarea>
    </div>
    <div class="properties-wrapper">
        Streams Properties: <button onclick="addNewProperty();">Add a new property</button>
        <div class="properties" id="properties">
        </div>
    </div>
    <br />
    <div>
        <button onclick="streamedResponse = false; sendRequest('/ksql');">Send Request!</button>
            &nbsp;&nbsp;(examples: "LIST STREAMS;", "SHOW QUERIES;", "CREATE STREAM foo AS SELECT baz FROM bar;")
        <br />
        <br />
        <button onclick="streamedResponse = true; sendRequest('/query');">Stream a query!</button>
            &nbsp;&nbsp;(example: "SELECT * FROM foo;", "PRINT topic_foo;")
        <br />
        <br />
        <span class="loading" id="request_loading" hidden>Request loading...</span>
        <button hidden id="cancel_request" onclick="xhr.abort();">Cancel request...</button>
        <br />
        <br />
        <br />
        Server response:
        <br />
        <br />
        Output format (not supported with topic printing):
            <label>
                <input checked type="radio" name="output" value="tabular" onchange="updateFormat(renderTabular);"> Tabular
            </label>
            <label>
                <input type="radio" name="output" value="prettyjson" onchange="updateFormat(renderPrettyJson);"> JSON (pretty)
            </label>
            <label>
                <input type="radio" name="output" value="compactjson" onchange="updateFormat(renderCompactJson);"> JSON (compact)
            </label>
        <br />
        <br />
        <pre id="response" class="response"></pre>
    </div>
</body>
</html>
